package cn.nkpro.elcube.financial;

import cn.nkpro.elcube.co.NkAbstractApplyCSO;
import cn.nkpro.elcube.utils.DateTimeUtilz;
import com.google.common.util.concurrent.AtomicDouble;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

public abstract class NkAbstractFinanceMethod extends NkAbstractApplyCSO {
    @Override
    public abstract Object apply(Object options) throws Exception;

    public List<ProgramDef> programDefs(){
        return null;
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public static class ProgramDef implements Serializable {
        private String key;
        private String name;
        private boolean effectiveDate;
        private boolean effectiveFromTo;
        private boolean pay;
        private boolean rate;
        private boolean value;
    }


    protected Long nextDate(Calendar calendar, Integer step, Integer dayOfMonth){

        calendar.add(Calendar.MONTH, step+1);
        calendar.set(Calendar.DAY_OF_MONTH,0);
        int latestDayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
        if(dayOfMonth<latestDayOfMonth){
            calendar.set(Calendar.DAY_OF_MONTH,dayOfMonth);
        }
        return calendar.getTimeInMillis()/1000;
    }

    /**
     * 计算还款计划
     * @param payments payments             还款计划缓存
     * @param dayOfMonth dayOfMonth         还款日->调整为第一期还款日
     * @param dateOfInterest dateOfInterest 开始计息日期
     * @param fromPeriod fromPeriod         计算起始期次
     * @param fromDate fromDate             计算起始日期
     * @param ip ip
     * @param np np
     * @param pv pv
     * @param fv fv
     * @param tp tp
     * @param step step
     * @return 还款计划
     */
    protected List<NkFinancePaymentI> calcPmt(List<NkFinancePaymentI> payments, Integer dayOfMonth, Long dateOfInterest, Integer fromPeriod, Long fromDate, Double ip,
                                              Integer np, Double pv, Double fv, Integer tp, Integer step, Double ir, Integer isNextMonth){
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(fromDate*1000);
        if(tp == 1 && isNextMonth == 0){
            calendar.add(Calendar.MONTH, fromPeriod==0?0:0);
        }else{
            calendar.add(Calendar.MONTH, fromPeriod==0?-tp:0);
        }

        // 期间付款金额
        double pay = pmt(ip, np, pv, fv, tp);

        for(int i=0;i<np;i++){

            long prevExpireDate = calendar.getTimeInMillis()/1000;

            NkFinancePaymentI item = new NkFinancePaymentI();
            //如果是期初，并且固定还款日
//            if(tp == 1 && dayOfMonth > 0 && i == 0){
//                item.setExpireDate(nextDate(calendar,step,calendar.get(Calendar.DAY_OF_MONTH)));
//            }else{
//                item.setExpireDate(nextDate(calendar,step,dayOfMonth));
//            }
            item.setExpireDate(nextDate(calendar,step,dayOfMonth));
            item.setPeriod(i+1+fromPeriod);
            item.setPay(pay);

            item.setInterest(tp == 1 && i+fromPeriod==0 ? 0 : doubleValue(pv * ip));

            // 计算期初利息
            if(item.getPeriod() == 1 && dateOfInterest > fromDate){
                long dayValid = DateTimeUtilz.dateDiff(dateOfInterest,item.getExpireDate());
                long dayCount = DateTimeUtilz.dateDiff(item.getExpireDate(),prevExpireDate);
                item.setInterest(doubleValue(item.getInterest()+(pv * ip *  dayValid / dayCount)));
            }
            if(item.getPeriod() == 1 && dayOfMonth > 0){
                double dayIr = ir / 360;
                long dayCount = DateTimeUtilz.dateDiff(item.getExpireDate(),fromDate);
                item.setInterest(doubleValue(pv * dayIr * dayCount));
            }

            item.setPrincipal(doubleValue(pay-item.getInterest()));
            item.setResidual(doubleValue(pv-item.getPrincipal()));
            item.setFee(0d);
            item.setRemark(null);

            payments.add(item);

            pv = item.getResidual();
        }
        return payments;
    }

    /**
     * ir - interest rate per month
     * np - number of periods (months)
     * pv - present value
     * fv - future value (residual value)
     * type - 0 or 1 need to implement that
     */
    private Double pmt(double ir, int np, double pv, double fv, int type) {
        if(pv==0 || np==0)
            return 0d;
        if(ir<=0)
            return doubleValue(pv/np);
        return doubleValue(( ir * ( pv * Math.pow ( (ir+1), np ) - fv ) ) / ( ( ir * type + 1 ) * ( Math.pow ( (ir+1), np) -1 ) ));
    }
    protected Double doubleValue(Double value){
        return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).doubleValue();
    }

    /**
     * 清理足后一期残值
     * @param payments 还款计划
     * @param fv 残值
     */
    protected void clearLast(List<NkFinancePaymentI> payments, Double fv){
        if((fv==null || fv==0)&&!payments.isEmpty()){
            NkFinancePaymentI last = payments.get(payments.size()-1);
            last.setPrincipal(doubleValue(last.getPrincipal() + last.getResidual()));
            if(last.getInterest() > last.getResidual()){
                last.setInterest(doubleValue(last.getInterest() - last.getResidual()));
            }else{
                last.setPay(doubleValue(last.getPay() + last.getResidual()));
            }
            last.setResidual(0d);
        }
    }

    /*
     * 设置小数点位数
     */
    protected void scale(List<NkFinancePaymentI> payments, int precision, int roundingMode){
        if(precision!=2){
            payments.forEach(i->{
                double newPayValue = new BigDecimal(i.getPay()).setScale(precision, RoundingMode.valueOf(roundingMode)).doubleValue();
                double newIntValue = doubleValue(i.getInterest() - i.getPay() + newPayValue);
                i.setPay(newPayValue);
                i.setInterest(newIntValue);
            });
        }
    }


    protected Double calcInterest(List<NkFinanceRate> rates, long from, long to, long dayCount, double residual){

        if(CollectionUtils.isEmpty(rates)){
            return 0d;
        }

        List<NkFinanceRate> availableRates = rates.stream()
                .filter(r -> r.getActiveDate() > from && r.getActiveDate() < to)
                .collect(Collectors.toList());

        AtomicDouble rate = new AtomicDouble();
        rates.stream()
                .filter(r -> r.getActiveDate() <= from)
                .forEach(r -> rate.set(r.getRate()));

        if(CollectionUtils.isEmpty(availableRates)){
            return doubleValue(residual * rate.get());
        }

        AtomicDouble interest = new AtomicDouble(0d);
        AtomicLong fromDate = new AtomicLong(from);

        availableRates.forEach(r->{
            interest.addAndGet(residual * rate.get() * DateTimeUtilz.dateDiff(r.getActiveDate(),fromDate.get()) / dayCount );
            fromDate.set(r.getActiveDate());
            rate.set(r.getRate());
        });

        return doubleValue( interest.addAndGet(residual * rate.get() * DateTimeUtilz.dateDiff(to,fromDate.get()) / dayCount ));
    }

    /**
     * 根据利率表计算利息
     * @param rates rates
     * @param from from
     * @param to to
     * @param residual residual
     * @return interest
     */
    protected Double calcInterest(List<NkFinanceRate> rates, long from, long to, double residual){
        return this.calcInterest(rates, from, to, DateTimeUtilz.dateDiff(to,from), residual);
    }
}
